#include "main.h"
#include "resource.h"
#include <WindowsX.h>

#define ADDSERVER			"/AddServerMenu"
#define JOINSERVER			"/JoinServerMenu"
#define ADDMAPFILTER		"/AddMapFilter"

HANDLE hMenuAdd = NULL;
HANDLE hContactMenuAddMapFilter = NULL;

HANDLE g_hOptionsInit = NULL;
HANDLE g_hMenuCreation = NULL;
HANDLE g_hEventDblClick = NULL;
HANDLE g_hModulesLoaded = NULL;
HANDLE g_hSystemPreShutdown = NULL;

int oldStatus = ID_STATUS_OFFLINE;
int currentStatus = ID_STATUS_OFFLINE;

BOOL CALLBACK UserDetailsDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK AddMapFilterDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK SetExecutableDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);

HANDLE g_hFilterContact = 0;
static int OnAddMapFilter(WPARAM wParam, LPARAM lParam) {
	char mapfilter[MAX_MAPFILTER];
	DBVARIANT dbv;
	HANDLE hContact = (HANDLE)wParam;
	g_hFilterContact = hContact;
	DBGetContactSetting(hContact, PROTONAME, MAPFILTER, &dbv);
	strcpy(mapfilter, dbv.pszVal);
	DBFreeVariant(&dbv);
	HWND hwnd = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_MAPFILTER), NULL, AddMapFilterDlgProc);
	ShowWindow(hwnd, SW_SHOW);
	SetDlgItemText(hwnd, IDC_MAPS, mapfilter);
	return 0;
}


int OnJoinServer(WPARAM wParam, LPARAM lParam) {
	DBVARIANT dbv;
	char ip[MAX_IP];
	char port[MAX_PORT];
	char type[MAX_GAME];
	char nick[MAX_NICK];
	char path[MAX_PATH];
	char directory[MAX_PATH];
	HANDLE hContact = (HANDLE)wParam;
	DBGetContactSetting(hContact, PROTONAME, ADDRESS, &dbv);
	strcpy(ip, dbv.pszVal);
	DBGetContactSetting(hContact, PROTONAME, PORT, &dbv);
	strcpy(port, dbv.pszVal);
	DBGetContactSetting(hContact, PROTONAME, GAMETYPE, &dbv);
	strcpy(type, dbv.pszVal);
	DBGetContactSetting(hContact, "CList", "MyHandle", &dbv);
	strcpy(nick, dbv.pszVal);
	DBGetContactSettingString(0, PROTONAME, type, &dbv);
	strcpy(path, dbv.pszVal);
	strcpy(directory, dbv.pszVal);
	DBFreeVariant(&dbv);

	// cut off filename
	size_t len = strlen(directory);
	while (directory[len] != '\\' && len > 0) {
		directory[len] = '\0';
		len--;
	}
	char params[MAX_PATH];	// create parameters e.g. "+connect 127.0.0.1:27095"
	sprintf(params, "%s %s:%s", gameList[gameIndex(type)].cmd, ip, port);
	int result = (int)ShellExecute(NULL, "open", path, params, directory, SW_SHOWDEFAULT);
	if (result <= 32) {	// show error
		sprintf(params, "Couldn't start %s", path);	// reuse params variable
		PUShowMessage(params, SM_NOTIFY);
	} else { // create and show away/popup message
		char message[MAX_GAME + MAX_IP + MAX_PORT + 12];
		ZeroMemory(message, sizeof(message));
		sprintf(message, "Playing %s @ %s:%s", type, ip, port);
		CList_SetGlobalStatus(ID_STATUS_AWAY, message);
		PopUp_Playing(hContact, nick, message);
	}
	return 1;
}



static int OnDoubleClick(WPARAM wParam, LPARAM lParam) {
	if (strcmp(PROTONAME, (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)wParam, 0)) == 0) 
		return OnJoinServer(wParam, lParam);
	else
		return 0;
}


static int Service_SystemPreShutdown(WPARAM wParam, LPARAM lParam) {
	return 0;
}

static int Service_MenuPreBuild(WPARAM wParam, LPARAM lParam) {
	return 0;
}


static int Service_GetCaps(WPARAM wParam, LPARAM lParam) {
	if (wParam == PFLAGNUM_1)
		return PF1_BASICSEARCH;
	if (wParam == PFLAGNUM_2)
		return PF2_ONLINE | PF2_SHORTAWAY;
	if (wParam == PFLAG_UNIQUEIDTEXT)
		return (int)"Server Address";
	return 0;
}


static int Service_GetName(WPARAM wParam, LPARAM lParam) {
	lstrcpynA((char*)lParam, PROTONAME, wParam);
	return 0;
}


static int Service_LoadIcon(WPARAM wParam, LPARAM lParam) {
	switch (wParam & 0xFFFF) {
	case PLI_PROTOCOL:
		return (int)LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_MAIN), IMAGE_ICON, GetSystemMetrics(wParam & PLIF_SMALL ? SM_CXSMICON : SM_CXICON), GetSystemMetrics(wParam & PLIF_SMALL ? SM_CYSMICON : SM_CYICON), 0);
	default:
		return (int)(HICON) NULL;
	}
}


static int Service_SetStatus(WPARAM wParam, LPARAM lParam) {
	oldStatus = currentStatus;
	currentStatus = wParam;
	ProtoBroadcastAck(PROTONAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, currentStatus);
	if (currentStatus == ID_STATUS_ONLINE) {
		CList_ForEachContact(CList_Update);
		Timer_Start(options.dwUpdateInterval);
	} else if (currentStatus == ID_STATUS_OFFLINE || currentStatus == ID_STATUS_AWAY) {
		Timer_Stop();
		CList_ForEachContact(CList_SetOffline);
	}
	return 0;
}


static int Service_GetStatus(WPARAM wParam, LPARAM lParam) {
	return currentStatus;
}


static int Service_InitUserInfo(WPARAM wParam, LPARAM lParam) {
	char* szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, lParam, 0);
	HANDLE hContact = (HANDLE)lParam;
	if (!hContact || !szProto || lstrcmpiA(szProto, PROTONAME))
		return 0;

	OPTIONSDIALOGPAGE odp = { 0 };
	odp.cbSize = sizeof(odp);
	odp.pszTitle = PROTONAME;
	odp.hIcon = NULL;
	odp.hInstance = g_hInstance;
	odp.position = -1900000000;
	odp.pfnDlgProc = UserDetailsDlgProc;
	odp.pszTemplate = MAKEINTRESOURCEA(IDD_DETAILS);
	odp.pszTitle = PROTONAME;
	CallService(MS_USERINFO_ADDPAGE, wParam, (LPARAM)&odp);
	return 0;
}


static void __cdecl AckBasicSearch(void * pszNick) {
	ProtoBroadcastAck(PROTONAME, NULL, ACKTYPE_SEARCH, ACKRESULT_INITIALISING, (HANDLE)1, 0);
	PROTOSEARCHRESULT psr;
	ZeroMemory(&psr, sizeof(psr));
	psr.cbSize = sizeof(psr);
	psr.nick = (char*)pszNick;
	// extract address
	string ip(psr.nick);
	string::size_type pos = ip.find_last_of(":");
	WORD port = 0;
	if (pos != string::npos) {
		port = atoi(ip.substr(pos + 1).c_str());
		ip = ip.substr(0, pos);
	}
	GS_SERVERINFO* info = NULL;
	GSSetOptInt(GS_OPTION_TIMEOUT, options.dwSearchTimeout);	// set new timeout
	for (int i = 0; i < sizeof(gameList) / sizeof(Game); i++) {
		info = GSQueryServer(gameList[i].type, ip.c_str(), port);
		if (info) {
			char p[MAX_PORT];
			psr.nick = info->host;
			psr.firstName = (char*)ip.c_str();
			psr.lastName = itoa(port, p, 10);
			psr.email = gameList[i].name;
			ProtoBroadcastAck(PROTONAME, NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)1, (LPARAM)&psr);
			GSFreeServerInfo(info);
		}
	}
	GSSetOptInt(GS_OPTION_TIMEOUT, options.dwUpdateTimeout);	// set old timeout
	ProtoBroadcastAck(PROTONAME, NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)1, 0);
}


static int Service_BasicSearch(WPARAM wParam, LPARAM lParam) {
	static char buf[MAX_IP + MAX_PORT + 2];
	if (lParam) {
		lstrcpynA(buf, (const char*)lParam, sizeof(buf));
		mir_forkthread(AckBasicSearch, &buf);
		return 1;
	}
	return 0;
}


static int Service_AddToList(WPARAM wParam, LPARAM lParam) {
	PROTOSEARCHRESULT* psr = (PROTOSEARCHRESULT*)lParam;
	HANDLE hContact = CList_AddContact(psr->firstName, psr->lastName, psr->email, psr->nick);
	CList_Update(hContact);
	return (int)hContact;
}


static void InitMenus(void) {
	char temp[MAXMODULELABELLENGTH];
	char *d = temp + sprintf(temp, PROTONAME);

	CLISTMENUITEM mi = {0};
	mi.cbSize = sizeof(mi);
	mi.pszService = temp;
	mi.flags = CMIF_ICONFROMICOLIB;

	mi.pszName = LPGEN("&Connect");
	mi.icolibItem = GetIconHandle(IDI_CONNECT);
	strcpy(d, JOINSERVER);
	mi.pszContactOwner = PROTONAME;
	mi.popupPosition = 500090001;
	hContactMenuAddMapFilter = (void*)CallService(MS_CLIST_ADDCONTACTMENUITEM, (WPARAM)0, (LPARAM)&mi);

	mi.pszName = LPGEN("&Add Map Filter");
	mi.icolibItem = GetIconHandle(IDI_FILTER);
	strcpy(d, ADDMAPFILTER);
	mi.pszContactOwner = PROTONAME;
	mi.popupPosition = 500090001;
	hContactMenuAddMapFilter = (void*)CallService(MS_CLIST_ADDCONTACTMENUITEM, (WPARAM)0, (LPARAM)&mi);
}


static int Service_ModulesLoaded(WPARAM wParam,LPARAM lParam) {
	AddIcons();
	InitMenus();
	HookEvent(ME_CLIST_DOUBLECLICKED, OnDoubleClick);
	HookEvent(ME_USERINFO_INITIALISE, Service_InitUserInfo);
	g_hOptionsInit = HookEvent(ME_OPT_INITIALISE, InitOptions);
	g_hMenuCreation = HookEvent(ME_CLIST_PREBUILDCONTACTMENU, Service_MenuPreBuild);
	Timer_Start(options.dwUpdateInterval);
	return 0;
}


void HookEvents(void) {
	g_hModulesLoaded = HookEvent(ME_SYSTEM_MODULESLOADED, Service_ModulesLoaded);
	g_hSystemPreShutdown = HookEvent(ME_SYSTEM_PRESHUTDOWN, Service_SystemPreShutdown);
}

void UnhookEvents(void) {
	UnhookEvent(g_hModulesLoaded);
	UnhookEvent(g_hSystemPreShutdown);
	UnhookEvent(g_hMenuCreation);
}

/////////////////////////////////////////////////////////////////////////////////////////
// Service function creation

static void CreateProtoService(const char* serviceName, MIRANDASERVICE pFunc) {
	char temp[MAXMODULELABELLENGTH];
	mir_snprintf(temp, sizeof(temp), "%s%s", PROTONAME, serviceName);
		CreateServiceFunction(temp, pFunc);
}

void CreateServiceFunctions() {
	CreateProtoService(PS_ADDTOLIST, Service_AddToList);
	CreateProtoService(PS_BASICSEARCH, Service_BasicSearch);
	CreateProtoService(PS_GETCAPS, Service_GetCaps);
	CreateProtoService(PS_GETNAME, Service_GetName);
	CreateProtoService(PS_GETSTATUS, Service_GetStatus);
	CreateProtoService(PS_LOADICON, Service_LoadIcon);
	CreateProtoService(PS_SETSTATUS, Service_SetStatus);
	CreateProtoService(JOINSERVER, OnJoinServer);
	CreateProtoService(ADDMAPFILTER, OnAddMapFilter);
}


BOOL CALLBACK AddMapFilterDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) {
	char mapfilter[MAX_MAPFILTER];
	ZeroMemory(mapfilter, sizeof(mapfilter));
	switch (msg) {
		case WM_INITDIALOG: {
			TranslateDialogDefault(hwndDlg);
			break;
		}
		case WM_COMMAND: {
			switch(HIWORD(wParam)) {
				case BN_CLICKED: {
					switch(LOWORD(wParam)) {
						case IDOK: {
							GetDlgItemText(hwndDlg, IDC_MAPS, mapfilter, MAX_MAPFILTER);
							mapfilter[MAX_MAPFILTER - 1] = '\0';
							CList_AddMapFilter(g_hFilterContact, mapfilter);
							g_hFilterContact = 0;
							SendMessage(hwndDlg, (WPARAM)WM_CLOSE, 0, 0);
							break;
						}
						case IDCANCEL: {
							SendMessage(hwndDlg, (WPARAM)WM_CLOSE, 0, 0);
							break;
						}
						default:
							break;
					}
				}
				break;
			}
			break;
		}
		case WM_CLOSE: {
			DestroyWindow(hwndDlg);
			break;
		}
		default:
			break;
	}
	return 0;
}


BOOL CALLBACK UserDetailsDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	char ip[MAX_IP];
	char port[MAX_PORT];
	char game[MAX_GAME];
	switch (msg) {
		case WM_INITDIALOG: {
			DBVARIANT dbv;
			HANDLE hContact = (HANDLE)lParam;
			TranslateDialogDefault(hwnd);
			DBGetContactSetting(hContact, PROTONAME, ADDRESS, &dbv);
			strcpy(ip, dbv.pszVal);
			DBGetContactSetting(hContact, PROTONAME, PORT, &dbv);
			strcpy(port, dbv.pszVal);
			DBGetContactSetting(hContact, PROTONAME, GAMETYPE, &dbv);
			strcpy(game, dbv.pszVal);
			SetDlgItemText(hwnd, IDC_GAMENAME, game);
			char address[MAX_IP + MAX_PORT];
			sprintf(address, "%s:%s", ip, port);
			SetDlgItemText(hwnd, IDC_HOSTADDRESS, address);
			GS_SERVERINFO *info;
			const GS_PLAYERINFO *player;
			info = GSQueryServer(gameList[gameIndex(game)].type, ip, atoi(port));

			if (info) {
				if (currentStatus != ID_STATUS_OFFLINE) {
					if (info->numplayers <= 0) {
						CList_SetAway(hContact);
					} else {
						CList_SetOnline(hContact);
					}
				}
				SetDlgItemText(hwnd, IDC_MODNAME, info->mod);
				SetDlgItemText(hwnd, IDC_MAPNAME, info->map);
				GSStripColor(gameList[gameIndex(game)].type, info->host, info->host);
				SetDlgItemText(hwnd, IDC_HOSTNAME, info->host);
				char clients[6];
				sprintf(clients, "%d/%d", info->numplayers, info->maxplayers);
				clients[5] = '\0';
				SetDlgItemText(hwnd, IDC_CLIENTS, clients);
				SetDlgItemText(hwnd, IDC_SCORELIMIT, itoa(info->scorelimit, clients, 10));
				SetDlgItemText(hwnd, IDC_TIMELIMIT, itoa(info->timelimit, clients, 10));

				HWND hwndListView = GetDlgItem(hwnd, IDC_PLAYERS);
				for(player = info->players; player != NULL; player = player->next) {
					GSStripColor(gameList[gameIndex(game)].type, player->name, player->name);
					ListBox_InsertItemData(hwndListView, -1, player->name);
				}
				GSFreeServerInfo(info);
			} else {
				CList_SetOffline(hContact);
			}
		}
		default:
			break;
	}
	return 0;
}
